package org.spider.core.executor.shape;


import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.assertj.core.util.Arrays;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spider.api.context.CookieContext;
import org.spider.api.context.SpiderContext;
import org.spider.api.context.SpiderContextHolder;
import org.spider.api.domain.utilDomain.Grammer;
import org.spider.api.domain.utilDomain.SpiderNode;
import org.spider.api.executor.ShapeExecutor;
import org.spider.api.expression.interfaces.Grammerable;
import org.spider.api.listener.TaskListener;
import org.spider.api.utils.http.HttpRequest;
import org.spider.api.utils.http.HttpResponse;
import org.spider.api.utils.http.SpiderResponse;
import org.spider.core.utils.ExpressionUtils;
import org.spider.core.utils.UserAgentUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 请求执行器
 *
 * @author Administrator
 */
@Component
public class RequestExecutor implements ShapeExecutor, TaskListener, Grammerable {

    public static final String LAST_EXECUTE_TIME = "__last_execute_time_";
    public static final String SLEEP = "sleep";
    public static final String RETRY_COUNT = "retryCount";
    public static final String RETRY_INTERVAL = "retryInterval";
    public static final String URL = "url";
    public static final String REPEAT_ENABLE = "repeatEnable";
    public static final String TIMEOUT = "timeout";
    public static final String REQUEST_METHOD = "method";
    public static final String COOKIE_AUTO_SET = "cookieAutoSet";
    public static final String COOKIE = "cookie";
    public static final String HEADER = "header";
    public static final String PROXY = "proxy";
    public static final String PARAMETER = "parameter";
    public static final String RESPONSE_CHARSET = "responseCharset";

    public static final String USER_AGENT = "User-Agent";
    @Value("${spider.workplace}")
    private String workspcace;


    private static final Logger logger = LoggerFactory.getLogger(RequestExecutor.class);

    @Override
    public String supportShape() {
        return "request";
    }

    @PostConstruct
    void init() {
        //允许设置被限制的请求头
        System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
    }

    @Resource
    StringRedisTemplate stringRedisTemplate;

    /**
     * 待调整，应该先进行url去重
     *
     * @param node      当前要执行的爬虫节点
     * @param context   爬虫上下文
     * @param variables 节点流程的全部变量的集合
     */
    @Override
    public void execute(SpiderNode node, SpiderContext context, Map<String, Object> variables) {
        String sleepCondition = node.getStringByKey(SLEEP);
        //1. 睡眠
        if (StringUtils.isNotBlank(sleepCondition)) {
            try {
                long sleepTime = NumberUtils.toLong(sleepCondition, 0L);
                synchronized (node.getNodeId().intern()) { //intern 放入常量池，减少使用时间
                    //实际等待时间 = 上次执行时间 + 睡眠时间 - 当前时间
                    Long lastExecuteTime = context.get(LAST_EXECUTE_TIME + node.getNodeId(), 0L);
                    if (lastExecuteTime != 0) {
                        sleepTime = lastExecuteTime + sleepTime - System.currentTimeMillis();
                    }
                    if (sleepTime > 0) {
                        context.pause(node.getNodeId(), "common", SLEEP, sleepTime);
                        logger.info("设置延迟时间:{}ms", sleepTime);
                        Thread.sleep(sleepTime);
                    }
                    context.put(LAST_EXECUTE_TIME + node.getNodeId(), System.currentTimeMillis());
                }
            } catch (Throwable t) {
                logger.error("设置延迟时间失败", t);
            }
        }
        //2. 重试次数
        int retryCount = NumberUtils.toInt(node.getStringByKey(RETRY_COUNT), 0) + 1;
        //3. 重试间隔时间，单位毫秒
        int retryInterval = NumberUtils.toInt(node.getStringByKey(RETRY_INTERVAL), 0);
        boolean successed = false;
        for (int i = 0; i < retryCount && !successed; i++) {
            HttpRequest request = HttpRequest.create();
            //4. 设置请求url
            String url = null;
            try {
                url = ExpressionUtils.execute(node.getStringByKey(URL), variables).toString();
                new URL(url); //报错就表示是个错误的url
            } catch (Exception e) {
                logger.error("设置请求url:{}出错，异常信息", url, e);
                return;
            }
            //过滤重复url
            if ("true".equalsIgnoreCase(node.getStringByKey(REPEAT_ENABLE, "true"))) {
                Boolean isContain = stringRedisTemplate.opsForSet().isMember(context.getId(), url);
                if (Boolean.TRUE.equals(isContain)) {
                    logger.info("过滤重复URL:{}", url);
                    return;
                }
            }
            context.pause(node.getNodeId(), "common", URL, url);
            logger.info("设置请求url:{}", url);
            request.url(url);
            //5. 设置请求超时时间
            int timeout = NumberUtils.toInt(node.getStringByKey(TIMEOUT), 60000);
            logger.debug("设置请求超时时间:{}", timeout);
            request.timeout(timeout);
            //6. 设置请求方法
            String method = Objects.toString(node.getStringByKey(REQUEST_METHOD, "GET"));
            logger.debug("设置请求方法:{}", method);
            request.method(method);
            //7. 设置header todo
            setRequestHeader(request, node);
            //8. 设置本节点Cookie
            setCookie(request, node, context);
            //设置请求参数
            setRequestParameter(request, node.getMapByKey(PARAMETER));
            //设置代理  host:port
//            String proxy = node.getStringByKey(PROXY);
//            if (StringUtils.isNotBlank(proxy)) {
//                try {
//                    String[] proxyArr = proxy.split(":");
//                    if (proxyArr.length == 2) {
//                        request.proxy(proxyArr[0], Integer.parseInt(proxyArr[1]));
//                        logger.info("设置代理：{}", proxy);
//                    }
//                } catch (Exception e) {
//                    logger.error("设置代理出错,异常信息:{}", e);
//                }
//            }
            Throwable exception = null;
            try {
                request.header(USER_AGENT, UserAgentUtils.getRandomUserAgent());
                HttpResponse response = request.execute();
                successed = response.getStatusCode() == 200;
                if (successed) {
                    //redis加入集合中
                    stringRedisTemplate.opsForSet().add(context.getId(), url);
                    String charset = node.getStringByKey(RESPONSE_CHARSET);
                    if (StringUtils.isNotBlank(charset)) {
                        response.setCharset(charset);
                        logger.debug("设置response charset:{}", charset);
                    }
                    //cookie存入cookieContext
                    context.getCookieContext()
                            .putAll(response.getCookies());
//                    logger.info("获取到新的cookie:{}",response.getCookies().toString());
                    logger.info("成功访问页面url:{}", response.getUrl());
                    //结果存入变量
                    variables.put("resp", response);
                }
            } catch (IOException e) {
                exception = e;
            } finally {
                //处理不成功的情况
                if (!successed) {
                    if (i + 1 < retryCount) {
                        //还可以重试
                        if (retryInterval > 0) {
                            try {
                                Thread.sleep(retryInterval);
                            } catch (InterruptedException ignored) {
                            }
                        }
                        logger.info("第{}次重试:{}", i + 1, url);
                        //不能重试，已经失败了
                    } else {
                        //记录访问失败的日志
                        if (context.getFlowId() != null) { //测试环境
                            File file = new File(workspcace, context.getFlowId() + File.separator + "logs" + File.separator + "access_error.log");
                            logger.info("在文件:{},写入错误日志", file.getAbsolutePath());
                            try {
                                File directory = file.getParentFile();
                                if (!directory.exists()) {
                                    directory.mkdirs();
                                }
                                FileUtils.write(file, url + "\r\n", "UTF-8", true); //写一行，添加到结尾
                            } catch (IOException ignored) {
                            }
                        }
                        logger.error("请求{}出错,异常信息:{}", url, exception);
                        variables.put("ex", exception);  //加入异常存储,其后面的节点无法执行
                    }
                }
            }
        }
    }

    private void setRequestParameter(HttpRequest request, Map<String, String> parameters) {
        if (parameters != null) {
            request.data(parameters);
        }
    }

    private void setCookie(HttpRequest request, SpiderNode node, SpiderContext context) {
        //设置自动管理的Cookie
        CookieContext cookieContext = context.getCookieContext();
        boolean cookieAutoSet = !"false".equals(node.getStringByKey(COOKIE_AUTO_SET));
        if (cookieAutoSet && !cookieContext.isEmpty()) {
            request.cookies(cookieContext);
            logger.debug("自动设置Cookie：{}", cookieContext);
        }
        String cookieStr = node.getStringByKey(COOKIE);
        Map<String,String> cookies = string2MapHelper(cookieStr);
        if(cookies==null) return;
        if (!cookies.isEmpty()) {
            request.cookies(cookies);
            logger.info("设置Cookie：{}", cookies.toString());
            if (cookieAutoSet) {
                cookieContext.putAll(cookies);
            }
        }
    }

    private void setRequestHeader(HttpRequest request,SpiderNode node) {
        String headerStr = node.getStringByKey(HEADER);
        Map<String,String> headers=string2MapHelper(headerStr);
        if (headers != null && !headers.isEmpty()) {
            logger.info("设置header:{}",headers.toString());
            request.headers(headers);
        }
    }

    /**
     * 用";"分割的键值对，并且键值对以"="分割<br>
     * 例如：Hm_lvt_b147be33903fb4b5cd5f16843ab81a1d=1717052406;Hm_lpvt_b147be33903fb4b5cd5f16843ab81a1d=1717053255
     * @param str
     * @return
     */
    private Map<String,String> string2MapHelper(String str){
        Map<String,String> map=null;
        if(StringUtils.isEmpty(str)) return null;
        map=new HashMap<>();
        String[] kvs = str.split(";");
        if(Arrays.isNullOrEmpty(kvs)) return null;
        for(String kv:kvs){
            String[] keyValue= kv.split("=");
            if(keyValue.length != 2) continue;
            map.put(keyValue[0],keyValue[1]);
        }
        return map;
    }

    @Override
    public void beforeListener() {
    }

    @Override
    public void afterListener() {
        SpiderContext context = SpiderContextHolder.get();
        stringRedisTemplate.delete(context.getId()); //删掉该数据库
        logger.info("清除redis哈希集合,释放内存");
    }

    @Override
    public List<Grammer> grammers() {
        String function = "resp";
        String owner = "SpiderResponse";
        List<Grammer> grammers = Grammer.findGrammers(SpiderResponse.class, function, owner, false);
        Grammer grammer = new Grammer();
        grammer.setFunction(function);
        grammer.setComment("抓取结果");
        grammer.setOwner(owner);
        grammers.add(grammer);
        return grammers;
    }

}